/* **********************************************************
 * Copyright (c) 2012-2014 Google, Inc.  All rights reserved.
 * Copyright (c) 2003-2008 VMware, Inc.  All rights reserved.
 * **********************************************************/

/*
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * * Neither the name of VMware, Inc. nor the names of its contributors may be
 *   used to endorse or promote products derived from this software without
 *   specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */

/*
 * DRview.c
 *
 * command-line tool for determining what is running under DR
 *
 * (c) araksha, inc. all rights reserved
 */


#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <tchar.h>

/* The order here now matters: share.h needs to be the first to include
 * globals_shared.h, as it has to set HOT_PATCHING_INTERFACE first;
 * but ntdll.h now includes globals_shared.h, so we put it second.
 * But, processes.h duplicates _VM_COUNTERS from ntdll.h but can handle
 * going 2nd but not first: so it must go last.
 */
#include "share.h"
#include "ntdll.h"
#include "processes.h"

#define NAME "DR"

int procwalk();
void dllwalk();

void
usage()
{
    fprintf(stderr, "Usage:\n");
    fprintf(stderr, "DRview [-help] [-pid n] [-exe name] [-listdr] [-listall] [-listdlls] [-showdlls] [-nopid] [-no32] [-out file] [-cmdline] [-showmem] [-showtime] [-nobuildnum] [-qname strip] [-noqnames] [-hot_patch] [-s n] [-tillidle] [-idlecpu c] [-showmemfreq f] [-idleafter s] [-v]\n");
    exit(1);
}

void
help()
{
    fprintf(stderr, "Options:\n");
    fprintf(stderr, " -pid n\t\t\tdisplays whether the process is injected into\n");
    fprintf(stderr, " -exe name\t\tfinds all processes whose executable matches\n");
    fprintf(stderr, "\t\t\t'name', shows whether injected into\n");
    fprintf(stderr, " -listdr\t\tlist all processes injected into\n");
    fprintf(stderr, " -listall\t\tlist all processes on the system, show whether injected_into\n");
    fprintf(stderr, " -listdlls\t\tlist all DLLs [short] for a specific pid or executable\n");
    fprintf(stderr, " -showdlls\t\tlist all DLLs [long] for a specific pid or executable\n");
    fprintf(stderr, " -nopid\t\t\tdoes not display PIDs of processes (useful for expect files)\n");
# ifdef X64
    fprintf(stderr, " -no32\t\t\tdoes not display whether 32-bit\n");
# endif
    fprintf(stderr, " -onlypid\t\tonly shows PID\n");
    fprintf(stderr, " -out file\t\toutput to file instead of stdout\n");
    fprintf(stderr, " -cmdline\t\tshow process command lines\n");
    fprintf(stderr, " -showmem\t\tshow memory stats\n");
    fprintf(stderr, " -showtime\t\tshow scheduled time for each process (needs -showmem)\n");
    fprintf(stderr, " -showstats\t\tshow internal stats\n");
    fprintf(stderr, " -nobuildnum\t\tdoes not display build number of SC (useful for expect files)\n");
    fprintf(stderr, " -qname strip\t\tshow qualified names; set strip to 0 or 1\n");
    fprintf(stderr, " -noqnames\t\tdon\'t show qualified names\n");
    fprintf(stderr, " -hot_patch\t\tshow hot patch status\n");
    fprintf(stderr, " -s n\t\t\tsample every n millis (default: 500ms, e.g. 1s=1000, 5min=300000 5*60*1000)\n");
    fprintf(stderr, " -tillidle\t\tsample until idle (default: < 3%% cpu for 3s)\n");
    fprintf(stderr, " -idlecpu c\t\tconsider < c%% cpu utilization idle (default: 3%%)\n");
    /* Sometimes -listall -showmem causes lsass %cpu to remain high, so
     * we provide an option to sample frequently but query lsass less frequently.
     */
    fprintf(stderr, " -showmemfreq f\t\tfor -tillidle -showmem, show -showmem every f samples (default: 1)\n");
    fprintf(stderr, " -idleafter s\t\tflag machine is idle after s seconds (default: 3s)\n");
    fprintf(stderr, " -v\t\t\tdisplay version information\n\n");

    exit(1);
}

BOOL listdr=FALSE, listall=FALSE, nopid=FALSE, no32=FALSE, onlypid=FALSE,
     listdlls=FALSE, showdlls=FALSE,
     qname=FALSE, strip=FALSE, noqnames=FALSE;
     cmdline=FALSE,
     hotp=FALSE,
     showtime=FALSE,
     sampling=FALSE,
     tillidle=FALSE,
     idle=FALSE,
     skip=FALSE,
     showmem=FALSE, showbuild=TRUE,
     showstats=FALSE;
uint pid=0;
uint millis=0;
uint idlecpu=0;
uint showmemfreq=1;
uint flag_after_ms=0;
char *exe=NULL, *outf=NULL;
process_id_t save_pid = 0;
WCHAR save_module[MAX_PATH];
WCHAR wexe[MAX_PATH];
FILE *fp;
LONGLONG total_user = 0;
LONGLONG total_kernel = 0;

#define MAX_CMDLINE 2048

#define BUFFER_SIZE_BYTES(buf)      sizeof(buf)
#define BUFFER_SIZE_ELEMENTS(buf)   (BUFFER_SIZE_BYTES(buf) / sizeof(buf[0]))
#define BUFFER_LAST_ELEMENT(buf)    buf[BUFFER_SIZE_ELEMENTS(buf) - 1]
#define NULL_TERMINATE_BUFFER(buf)  BUFFER_LAST_ELEMENT(buf) = 0
#define NULL_TERMINATE_SIZED_BUFFER(buf, size) (buf)[(size)-1] = 0

static void
generate_process_name(process_info_t *pi, WCHAR *name_buf /* OUT */,
                      uint name_buf_length /* elements */)
{
    WCHAR qual_name[MAX_CMDLINE];
    WCHAR qual_args[MAX_CMDLINE];
    int res;
    BOOL use_args = FALSE;

    /* hack: we assume only need qualified names for these hardcoded apps
     * FIXME: read registry to see whether need qualification
     */
    if (wcsicmp(pi->ProcessName, L"svchost.exe") == 0 ||
        wcsicmp(pi->ProcessName, L"msiexec.exe") == 0 ||
        wcsicmp(pi->ProcessName, L"tomcat.exe") == 0 ||
        wcsicmp(pi->ProcessName, L"dllhost.exe") == 0) {
        /* use qualified names */
        res = get_process_cmdline(pi->ProcessID, qual_name,
                                  BUFFER_SIZE_ELEMENTS(qual_name));
        NULL_TERMINATE_BUFFER(qual_name);
        if (res == ERROR_SUCCESS) {
            if (get_commandline_qualifier(qual_name, qual_args,
                                          BUFFER_SIZE_ELEMENTS(qual_args),
                                          /* hack: we assume we only strip svchost,
                                           * and we also strip dllhost here to
                                           * fit more of the GUI in and avoid the
                                           * "Processid" string.
                                           * FIXME: read from registry.
                                           */
                                          (wcsicmp(pi->ProcessName, L"svchost.exe") != 0 &&
                                           wcsicmp(pi->ProcessName, L"dllhost.exe") != 0))) {
                NULL_TERMINATE_BUFFER(qual_args);
                use_args = TRUE;
            } /* else not an error, just no args. e.g.: plain sqlservr.exe */
        } else {
            /* this is an error => notify user */
            _snwprintf(qual_args, BUFFER_SIZE_ELEMENTS(qual_args), L"<error>");
            NULL_TERMINATE_BUFFER(qual_args);
            use_args = TRUE;
        }
    }
    if (use_args) {
        _snwprintf(name_buf, name_buf_length, L"%s-%s",
                   pi->ProcessName, qual_args);
    } else {
        _snwprintf(name_buf, name_buf_length, L"%s", pi->ProcessName);
    }
    NULL_TERMINATE_SIZED_BUFFER(name_buf, name_buf_length);
}

static void
print_mem_stats(process_info_t *pi, char reschar, int version)
{
    WCHAR qual_name[MAX_CMDLINE];
    int cpu = -1, user = -1;
    LONGLONG wallclock_time = get_system_time() - pi->CreateTime.QuadPart;
    LONGLONG scheduled_time = pi->UserTime.QuadPart + pi->KernelTime.QuadPart;
    static LONGLONG firstproc_time = 0;
    if (wallclock_time != (LONGLONG) 0) {
        cpu = (int) ((100 * scheduled_time) / wallclock_time);
    }
    if (scheduled_time != (LONGLONG) 0) {
        user = (int) ((100 * pi->UserTime.QuadPart) / scheduled_time);
    }

    /* Total user and kernel time scheduled for all processes.  Don't include idle
     * process, and if -skip option is specified don't include DRview.exe */
    if (wcsicmp(pi->ProcessName, L"") != 0
        && (wcsicmp(pi->ProcessName, L"drview.exe") != 0 || !skip)) {
        total_user   += pi->UserTime.QuadPart;
        total_kernel += pi->KernelTime.QuadPart;
    }

    /* CreateTime.QuadPart is a counter since 1916.  Both idle process and
     * System have create time of 0, so report create time, in ms, relative to
     * smss.exe */
    if (wcsicmp(pi->ProcessName, L"") == 0
        || wcsicmp(pi->ProcessName, L"system") == 0
        || wcsicmp(pi->ProcessName, L"smss.exe") == 0) {
        firstproc_time = pi->CreateTime.QuadPart;
    }

    generate_process_name(pi, qual_name, BUFFER_SIZE_ELEMENTS(qual_name));

    /* single line best so can line up columns and process output easily */
    fprintf(fp, "%-23.23S %5d %c %5d %2d%% %3d%% %5d %3d %7d %7d %8d %7d %7d %7d %7d %5d %5d %5d %5d %5d",
            qual_name, pi->ProcessID, reschar, version,
            cpu, user,
            pi->HandleCount, pi->ThreadCount,
            pi->VmCounters.PeakVirtualSize/1024,
            pi->VmCounters.VirtualSize/1024,
            pi->VmCounters.PeakPagefileUsage/1024,
            pi->VmCounters.PagefileUsage/1024, /* aka Private */
            pi->VmCounters.PeakWorkingSetSize/1024,
            pi->VmCounters.WorkingSetSize/1024,
            pi->VmCounters.PageFaultCount,
            pi->VmCounters.QuotaPeakPagedPoolUsage/1024,
            pi->VmCounters.QuotaPagedPoolUsage/1024,
            pi->VmCounters.QuotaPeakNonPagedPoolUsage/1024,
            pi->VmCounters.QuotaNonPagedPoolUsage/1024,
            pi->InheritedFromProcessID);
    if (showtime) {
        /* QuadPart is in ticks 1 tick = 100 nano secs. In ms = n * 100/(1000*1000) */
        fprintf(fp, " %15I64d %15I64d %15I64d",
                    (pi->UserTime.QuadPart/10000),
                    (pi->KernelTime.QuadPart/10000),
                    ((pi->CreateTime.QuadPart - firstproc_time)/10000));
    }
    fprintf(fp, "\n");
}

int
main(int argc, char **argv)
{
    int nprocs, argidx=1;

    if (argc < 2)
        usage();

    while (argidx < argc) {

        if (!strcmp(argv[argidx], "-help")) {
            help();
        }
        else if (!strcmp(argv[argidx], "-pid") || !strcmp(argv[argidx], "-p")) {
            if (argidx+1 >= argc)
                usage();
            pid=atoi(argv[++argidx]);
        }
        else if (!strcmp(argv[argidx], "-exe")) {
            if (argidx+1 >= argc)
                usage();
            exe=argv[++argidx];
            _snwprintf(wexe, MAX_PATH, L"%S", exe);
            wexe[MAX_PATH - 1] = L'\0';
        }
        else if (!strcmp(argv[argidx], "-listall")) {
            listall=TRUE;
        }
        else if (!strcmp(argv[argidx], "-listdr")) {
            listdr=TRUE;
        }
        else if (!strcmp(argv[argidx], "-listdlls")) {
            listdlls=TRUE;
        }
        else if (!strcmp(argv[argidx], "-showdlls")) {
            listdlls=TRUE;
            showdlls=TRUE;
        }
        else if (!strcmp(argv[argidx], "-nopid")) {
            nopid=TRUE;
        }
        else if (!strcmp(argv[argidx], "-no32")) {
            no32=TRUE;
        }
        else if (!strcmp(argv[argidx], "-onlypid")) {
            onlypid=TRUE;
        }
        else if (!strcmp(argv[argidx], "-out")) {
            if (argidx+1 >= argc)
                usage();
            outf=argv[++argidx];
        }
        else if (!strcmp(argv[argidx], "-cmdline")) {
            cmdline=TRUE;
        }
        else if (!strcmp(argv[argidx], "-showstats")) {
            showstats=TRUE;
        }
        else if (!strcmp(argv[argidx], "-showmem")) {
            showmem=TRUE;
        }
        else if (!strcmp(argv[argidx], "-showtime")) {
            if (!showmem)
                usage();
            showtime=TRUE;
        }
        else if (!strcmp(argv[argidx], "-nobuildnum")) {
            showbuild=FALSE;
        }
        else if (!strcmp(argv[argidx], "-qname")) {
            if (argidx+1 >= argc)
                usage();
            qname = TRUE;
            strip = atoi(argv[++argidx]);
        }
        else if (!strcmp(argv[argidx], "-noqnames")) {
            noqnames=TRUE;
        }
        else if (!strcmp(argv[argidx], "-hot_patch")) {
            hotp=TRUE;
        }
        else if (!strcmp(argv[argidx], "-s")) {
            millis = (uint)atoi(argv[argidx+1]);
            if (millis == 0) {
                millis = 500;
            }
            else {
                argidx++;
            }
            sampling=TRUE;
        }
        else if (!strcmp(argv[argidx], "-tillidle")) {
            millis = (millis <= 0) ? 500 : millis;
            idlecpu = (idlecpu <= 0) ? 3 : idlecpu;
            showmemfreq = (showmemfreq <= 1) ? 1 : showmemfreq;
            flag_after_ms = (flag_after_ms <= 0) ? 3000 : flag_after_ms;
            tillidle = TRUE;
            sampling = TRUE;
        }
        else if (!strcmp(argv[argidx], "-idlecpu")) {
            if (argidx+1 >= argc)
                usage();
            idlecpu = (uint)atoi(argv[++argidx]);
            idlecpu = (idlecpu <= 0) ? 3 : idlecpu;
            millis = (millis <= 0) ? 500 : millis;
            flag_after_ms = (flag_after_ms <= 0) ? 3000 : flag_after_ms;
            tillidle = TRUE;
            sampling = TRUE;
        }
        else if (!strcmp(argv[argidx], "-showmemfreq")) {
            if (argidx+1 >= argc)
                usage();
            showmemfreq = (uint)atoi(argv[++argidx]);
            showmemfreq = (showmemfreq <= 1) ? 1 : showmemfreq;
            showmem = TRUE;
            tillidle = TRUE;
            sampling = TRUE;
        }
        else if (!strcmp(argv[argidx], "-idleafter")) {
            if (argidx+1 >= argc)
                usage();
            flag_after_ms = (uint)atoi(argv[++argidx]);
            flag_after_ms = (flag_after_ms <= 0) ? 3000 : flag_after_ms;
            idlecpu = (idlecpu <= 0) ? 3 : idlecpu;
            millis = (millis <= 0) ? 500 : millis;
            sampling = TRUE;
            tillidle = TRUE;
        }
        else if (!strcmp(argv[argidx], "-skip")) {
            /* internal option: skip drview's contribution to total scheduled time */
            skip=TRUE;
        }
        else if (!strcmp(argv[argidx], "-v")) {
#if defined(BUILD_NUMBER) && defined(VERSION_NUMBER)
          printf("drview.exe version %s -- build %d\n", STRINGIFY(VERSION_NUMBER), BUILD_NUMBER);
#elif defined(BUILD_NUMBER)
          printf("drview.exe custom build %d -- %s\n", BUILD_NUMBER, __DATE__);
#else
          printf("drview.exe custom build -- %s, %s\n", __DATE__, __TIME__);
#endif
          exit(0);
        }
        else {
            fprintf(stderr, "Unknown option: %s\n", argv[argidx]);
            usage();
        }
        argidx++;
    }

    if (listdlls && !pid && !exe && !listall && !listdr)
    {
      fprintf(stderr, "-listdlls option should be combined with a specific -pid, -exe, -listdr or -listall option\n");
      usage();
    }

    if (outf) {
        fp = fopen(outf,"w");
        if(fp == NULL) {
            fprintf(stderr, "Error opening %s for output.\n");
            exit(-1);
        }
    }
    else
        fp=stdout;

    /* Invoke DLL routine when -listdlls combined with either -listall or -pid or -exe */
    if (listdlls && (listall || pid || exe || listdr))
    {
      dllwalk();
    }
    else
    {
        do {
            nprocs = procwalk();
            fflush(fp);
            if (sampling)
                Sleep(millis);
            /* FIXME: looping infinitely, make sure we are not leaking anything */
        } while (sampling && !idle);
    }

    if (outf)
        fclose(fp);

    return nprocs;
}

int count;

/* returns FALSE if status is effectively disabled, otherwise returns
 *  TRUE (indicating status should be displayed. */
BOOL
get_status_string(char *buf, UINT maxchars,
                  hotp_inject_status_t status,
                  hotp_policy_mode_t mode)
{
    char *statptr = NULL;
    char *modeptr = NULL;

    if (status == HOTP_INJECT_NO_MATCH && mode == HOTP_MODE_OFF)
        return FALSE;

    switch (status) {
    case HOTP_INJECT_ERROR :
        statptr = "Inject Error!";
        break;
    case HOTP_INJECT_PROTECT :
        statptr = "Injected Protector";
        break;
    case HOTP_INJECT_DETECT :
        statptr = "Injected Detector";
        break;
    case HOTP_INJECT_IN_PROGRESS :
        statptr = "Injection in progress";
        break;
    case HOTP_INJECT_PENDING :
        statptr = "Inject point not yet executed";
        break;
    case HOTP_INJECT_NO_MATCH :
        statptr = "Not matched";
        break;
    case HOTP_INJECT_OFF :
        /* not currently used. */
    default :
        statptr = NULL;
    }

    switch (mode) {
    case HOTP_MODE_OFF :
        modeptr = "Off";
        break;
    case HOTP_MODE_DETECT :
        modeptr = "Detect";
        break;
    case HOTP_MODE_PROTECT :
        modeptr = "Protect";
        break;
    default:
        modeptr = NULL;
    }

    if (statptr == NULL || modeptr == NULL) {
        _snprintf(buf, maxchars,
                  "[Status ERROR: Status=%d, Mode=%d]", status, mode);
    }
    else {
        _snprintf(buf, maxchars, "%s [%s]", modeptr, statptr);
    }

    buf[maxchars - 1] = '\0';

    return TRUE;
}


BOOL
pw_callback(process_info_t *pi, void **param)
{
    char *resstr;
    char reschar;
    int res;
    WCHAR buf[MAX_CMDLINE];
    DWORD version;
    BOOL under_dr;

    WCHAR qual_name[MAX_CMDLINE];
    if (exe)
        generate_process_name(pi, qual_name, BUFFER_SIZE_ELEMENTS(qual_name));

    if ( (pid && pi->ProcessID == pid) ||
         (exe && (!wcsicmp(wexe, pi->ProcessName) || !wcsicmp(wexe,  qual_name))) ||
         listall || listdr)
        {
            version = -1;
            res = under_dynamorio_ex(pi->ProcessID, &version);
            switch (res) {
            case DLL_PROFILE : resstr=NAME" profile"; reschar='P'; break;
            case DLL_RELEASE : resstr=NAME" release"; reschar='R'; break;
            case DLL_DEBUG : resstr=NAME" debug"; reschar='D'; break;
            case DLL_CUSTOM : resstr=NAME" custom"; reschar='C'; break;
            case DLL_NONE : resstr="native"; reschar='N'; break;
            case DLL_UNKNOWN :
            default : resstr="<error>"; reschar='?';
            }

            under_dr = !(res == DLL_NONE || res == DLL_UNKNOWN);

            if (!listdr || under_dr) {
                if (!nopid && !showmem) {
                    if (onlypid)
                        fprintf(fp, "%d\n", (DWORD) pi->ProcessID);
                    else
                        fprintf(fp, "PID %d, ", (DWORD) pi->ProcessID);
                }
                if (!showmem && !onlypid) {
                    WCHAR qual_name[MAX_CMDLINE];
                    WCHAR *name_to_use = pi->ProcessName;
#ifdef X64
                    HANDLE hproc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE,
                                               (DWORD) pi->ProcessID);
                    if (is_wow64(hproc)) {
                        if (!no32)
                            fprintf(fp, "32-bit, ");
                        /* FIXME: currently x64 process can't see 32-bit
                         * drmarker
                         */
                        resstr="<unknown>";
                    }
                    CloseHandle(hproc);
#endif
                    if (!noqnames) {
                        generate_process_name(pi, qual_name,
                                              BUFFER_SIZE_ELEMENTS(qual_name));
                        name_to_use = qual_name;
                    }
                    fprintf(fp, "Process %S, ", name_to_use);
                    if (version == -1 || !showbuild)
                        fprintf(fp, "running %s\n", resstr);
                    else
                        fprintf(fp, "running %s (build %d)\n", resstr, version);
                }
                if (cmdline) {
                    res = get_process_cmdline(pi->ProcessID, buf,
                                              BUFFER_SIZE_ELEMENTS(buf));
                    NULL_TERMINATE_BUFFER(buf);
                    if (res == ERROR_SUCCESS) {
                        fprintf(fp, "\tCmdline: %S\n", buf);
                    }
                    else {
                        /* acquiring SeDebugPrivilege requires being admin */
                        if (res == ERROR_NOT_ALL_ASSIGNED)
                            fprintf(fp, "\t<Re-run as administrator for cmdline>\n");
                        else
                            fprintf(fp, "\t<Cmdline err %d>\n", res);
                    }
                }
                if (qname) {
                    WCHAR cmdline[MAX_CMDLINE];
                    res = get_process_cmdline(pi->ProcessID,
                                              cmdline, BUFFER_SIZE_ELEMENTS(cmdline));
                    NULL_TERMINATE_BUFFER(cmdline);
                    if (res == ERROR_SUCCESS) {
                        if (!get_commandline_qualifier(cmdline, buf,
                                                       BUFFER_SIZE_ELEMENTS(buf), !strip))
                            buf[0] = L'\0'; /* no args */
                        NULL_TERMINATE_BUFFER(buf);
                    }
                    if (res == ERROR_SUCCESS)
                        fprintf(fp, "\tQname: %S%s%S\n", pi->ProcessName,
                                buf[0] == L'\0' ? "" : "-", buf);
                    else
                        fprintf(fp, "\t<Qname err %d>\n", res);

                }
                if (under_dr && hotp) {
                    hotp_policy_status_table_t *status_tbl = NULL;
                    res = get_hotp_status(pi->ProcessID, &status_tbl);
                    if (res == ERROR_SUCCESS) {
                        uint j;
                        hotp_policy_status_t *cur;
                        fprintf(fp, "\tHotpatching:\n", res);
                        for (j = 0; j < status_tbl->num_policies; j++) {
                            char status_buf[MAX_PATH];
                            cur = &(status_tbl->policy_status_array[j]);
                            if (get_status_string(status_buf, MAX_PATH,
                                                  cur->inject_status,
                                                  cur->mode))
                                fprintf(fp, "\t  Patch %s: %s\n",
                                        cur->policy_id, status_buf);
                        }
                    }
                    else if (res == ERROR_DRMARKER_ERROR) {
                        fprintf(fp, "\tHot Patching Not Enabled\n", res);
                    }
                    else {
                        fprintf(fp, "\t<Hotpatch Query Error %d>\n", res);
                    }
                }
                if (under_dr && showstats) {
                    dr_statistics_t *stats = get_dynamorio_stats(pi->ProcessID);
                    if (stats != NULL) {
                        uint i;
                        fprintf(fp, "\t%.*s\n",
                                BUFFER_SIZE_ELEMENTS(stats->process_name),
                                stats->process_name);
                        for (i = 0; i < stats->num_stats; i++) {
                            fprintf(fp, "\t%*.*s :%9d\n",
                                    BUFFER_SIZE_ELEMENTS(stats->stats[i].name),
                                    BUFFER_SIZE_ELEMENTS(stats->stats[i].name),
                                    stats->stats[i].name, stats->stats[i].value);
                        }
                    }
                    free_dynamorio_stats(stats);
                }
                if (showmem) {
                    print_mem_stats(pi, reschar, version);
                }
                count++;
            }
        }
    return TRUE;
}

BOOL
dllw_callback(module_info_t *mi, void **param)
{
  char *resstr;
  char reschar;
  int res;
  DWORD version;
  BOOL exe_match = (exe && wcsstr(mi->BaseDllName, wexe)) ? TRUE : FALSE;
  BOOL exe_present = (wcsstr(mi->BaseDllName, L".exe") || wcsstr(mi->BaseDllName, L".EXE")) ? TRUE : FALSE;

  /* -listdlls option is set && any of below 3 conditions hold true.
     (1) -pid value is set and current threadID matches.
     (2) -exe is set and all DLLs with matching threadIDs (a fake comparison) since WINDOWS provides no other handle.
     (3) -listall option is set
  */

  if ( listdlls && ((pid && mi->ProcessID == pid) ||
                    ((exe_match ||
                      (exe && save_pid == mi->ProcessID &&
                       (!exe_present || wcscmp(save_module, mi->BaseDllName) == 0)))) ||
                    (listall || listdr)))
    {
        version = -1;
        res = under_dynamorio_ex (mi->ProcessID, &version);
        switch (res) {
        case DLL_PROFILE : resstr=NAME" profile"; reschar='P'; break;
        case DLL_RELEASE : resstr=NAME" release"; reschar='R'; break;
        case DLL_DEBUG : resstr=NAME" debug"; reschar='D'; break;
        case DLL_CUSTOM : resstr=NAME" custom"; reschar='C'; break;
        case DLL_NONE : resstr="native"; reschar='N'; break;
        case DLL_UNKNOWN :
        default : resstr="<error>"; reschar='?';
        }

        save_pid = mi->ProcessID;

        if (!(listdr && (res==DLL_NONE || res==DLL_UNKNOWN))) {
            if (wcsstr(mi->BaseDllName, L".exe") || wcsstr(mi->BaseDllName, L".EXE"))
            {
                wcsncpy(save_module, mi->BaseDllName,
                        sizeof(save_module)/sizeof(save_module[0]));
                save_module[(sizeof(save_module)/sizeof(save_module[0]))-1] = L'\0';
                if (!nopid && !showmem)
                    fprintf(fp, "\n\nPID %d ", mi->ProcessID);
                if (!showmem) {
                    if (version == -1 || !showbuild) {
                        fprintf(fp, "\t\tProcess %S, running %s\n",
                                mi->BaseDllName,
                                resstr);
                    }
                    else {
                        fprintf(fp, "\t\tProcess %S, running %s (build %d)\n",
                                mi->BaseDllName,
                                resstr,
                                version);
                    }
                }
                count = 0;
            }
            else
            {
                if (!showmem) {
                    if (showdlls) {
                        /* long format */
                        fprintf(fp, "  %p-%p  %-16S Stamp=%x Count=%d\n    %S\n",
                                mi->BaseAddress, (char *)mi->BaseAddress+mi->SizeOfImage,
                                mi->BaseDllName, mi->TimeDateStamp,
                                mi->LoadCount, mi->FullDllName);
                    } else {
                        fprintf(fp, "\t%-16S",
                                mi->BaseDllName);
                        count++;
                        if ((count % 3) == 0) {
                            fprintf(fp, "\n");
                            count = 0;
                        }
                    }
                }
            }
        }
    }

  return TRUE;
}

int
procwalk()
{
    static int pwalk_per = 0;
    uint system_load = get_system_load(sampling);
    if (tillidle) {
        static uint idle_for_ms = 0;
        idle_for_ms = (system_load < idlecpu) ? idle_for_ms + millis : 0;
        idle = (idle_for_ms >= flag_after_ms);
    }

    count = 0;
    if (showmem) {
        SYSTEM_PERFORMANCE_INFORMATION sperf_info;
        if (get_system_performance_info(&sperf_info)) {
            fprintf (fp, "System committed memory (KB): %d / %d peak %d\n",
                     /* in pages so x4==KB */
                     4*sperf_info.TotalCommittedPages,
                     4*sperf_info.TotalCommitLimit,
                     4*sperf_info.PeakCommitment);
            /* FIXME: add physical memory, kernel memory, etc. */
        }
        fprintf (fp, "System load: %d%%\t\tUptime: %lu ms\n",
                 system_load, get_uptime());

        /* %user reaches 100% so we give it the extra column over %cpu */
        fprintf (fp, "%-23s %5s %7s %3s %4s %5s %3s %7s %7s %8s %7s %7s %7s %7s %5s %5s %5s %5s %5s",
                 "Name-Qualification", "PID", "DR  Bld", "CPU", "User", "Hndl", "Thr",
                 "PVSz", "VSz", "PPriv", "Priv",
                 "PWSS", "WSS", "Fault", "PPage", "Page", "PNonP", "NonP", "PPID");
        if (showtime)
            fprintf (fp, " %15s %15s %15s", "User Time(ms)", "Kernel Time(ms)",
                                            "Create Time(ms)");
        fprintf (fp, "\n");

    }
    if (pwalk_per == 0 || pwalk_per % showmemfreq == 0) {
        process_walk (&pw_callback, NULL);
        if (!count)
            fprintf (fp, "No such process found.\n");
        else {
            if (showmem && showtime) {
                fprintf (fp, "Total scheduled user/kernel time: %I64d/%I64d ms\n",
                         total_user/10000, total_kernel/10000);
            }
        }
    }
    pwalk_per++;

    total_user = 0;
    total_kernel = 0;

    return count;
}

void
dllwalk()
{
  count = 0;
  dll_walk_all(&dllw_callback, NULL);
}

